// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2025 MediaTek Inc.
 *
 * Author: Neal Yen <neal.yen@mediatek.com>
 * Author: Weijie Gao <weijie.gao@mediatek.com>
 */

#include <phy.h>
#include <miiphy.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/mdio.h>
#include <linux/mii.h>
#include "mtk_eth.h"

/* AN8855 Register Definitions */
#define AN8855_SYS_CTRL_REG			0x100050c0
#define AN8855_SW_SYS_RST			BIT(31)

#define AN8855_PMCR_REG(p)			(0x10210000 + (p) * 0x200)
#define AN8855_FORCE_MODE_LNK			BIT(31)
#define AN8855_FORCE_MODE			0xb31593f0

#define AN8855_PORT_CTRL_BASE			0x10208000
#define AN8855_PORT_CTRL_REG(p, r)		(AN8855_PORT_CTRL_BASE + (p) * 0x200 + (r))

#define AN8855_PORTMATRIX_REG(p)		AN8855_PORT_CTRL_REG(p, 0x44)

#define AN8855_PVC(p)				AN8855_PORT_CTRL_REG(p, 0x10)
#define AN8855_STAG_VPID_S			16
#define AN8855_STAG_VPID_M			0xffff0000
#define AN8855_VLAN_ATTR_S			6
#define AN8855_VLAN_ATTR_M			0xc0

#define VLAN_ATTR_USER				0

#define AN8855_INT_MASK				0x100050F0
#define AN8855_INT_SYS_BIT			BIT(15)

#define AN8855_RG_CLK_CPU_ICG			0x10005034
#define AN8855_MCU_ENABLE			BIT(3)

#define AN8855_RG_TIMER_CTL			0x1000a100
#define AN8855_WDOG_ENABLE			BIT(25)

#define AN8855_CKGCR				0x10213e1c

#define AN8855_SCU_BASE				0x10000000
#define AN8855_RG_RGMII_TXCK_C			(AN8855_SCU_BASE + 0x1d0)
#define AN8855_RG_GPIO_LED_MODE			(AN8855_SCU_BASE + 0x0054)
#define AN8855_RG_GPIO_LED_SEL(i)		(AN8855_SCU_BASE + (0x0058 + ((i) * 4)))
#define AN8855_RG_INTB_MODE			(AN8855_SCU_BASE + 0x0080)
#define AN8855_RG_GDMP_RAM			(AN8855_SCU_BASE + 0x10000)
#define AN8855_RG_GPIO_L_INV			(AN8855_SCU_BASE + 0x0010)
#define AN8855_RG_GPIO_CTRL			(AN8855_SCU_BASE + 0xa300)
#define AN8855_RG_GPIO_DATA			(AN8855_SCU_BASE + 0xa304)
#define AN8855_RG_GPIO_OE			(AN8855_SCU_BASE + 0xa314)

#define AN8855_HSGMII_AN_CSR_BASE		0x10220000
#define AN8855_SGMII_REG_AN0			(AN8855_HSGMII_AN_CSR_BASE + 0x000)
#define AN8855_SGMII_REG_AN_13			(AN8855_HSGMII_AN_CSR_BASE + 0x034)
#define AN8855_SGMII_REG_AN_FORCE_CL37		(AN8855_HSGMII_AN_CSR_BASE + 0x060)

#define AN8855_HSGMII_CSR_PCS_BASE		0x10220000
#define AN8855_RG_HSGMII_PCS_CTROL_1		(AN8855_HSGMII_CSR_PCS_BASE + 0xa00)
#define AN8855_RG_AN_SGMII_MODE_FORCE		(AN8855_HSGMII_CSR_PCS_BASE + 0xa24)

#define AN8855_MULTI_SGMII_CSR_BASE		0x10224000
#define AN8855_SGMII_STS_CTRL_0			(AN8855_MULTI_SGMII_CSR_BASE + 0x018)
#define AN8855_MSG_RX_CTRL_0			(AN8855_MULTI_SGMII_CSR_BASE + 0x100)
#define AN8855_MSG_RX_LIK_STS_0			(AN8855_MULTI_SGMII_CSR_BASE + 0x514)
#define AN8855_MSG_RX_LIK_STS_2			(AN8855_MULTI_SGMII_CSR_BASE + 0x51c)
#define AN8855_PHY_RX_FORCE_CTRL_0		(AN8855_MULTI_SGMII_CSR_BASE + 0x520)

#define AN8855_XFI_CSR_PCS_BASE			0x10225000
#define AN8855_RG_USXGMII_AN_CONTROL_0		(AN8855_XFI_CSR_PCS_BASE + 0xbf8)

#define AN8855_MULTI_PHY_RA_CSR_BASE		0x10226000
#define AN8855_RG_RATE_ADAPT_CTRL_0		(AN8855_MULTI_PHY_RA_CSR_BASE + 0x000)
#define AN8855_RATE_ADP_P0_CTRL_0		(AN8855_MULTI_PHY_RA_CSR_BASE + 0x100)
#define AN8855_MII_RA_AN_ENABLE			(AN8855_MULTI_PHY_RA_CSR_BASE + 0x300)

#define AN8855_QP_DIG_CSR_BASE			0x1022a000
#define AN8855_QP_CK_RST_CTRL_4			(AN8855_QP_DIG_CSR_BASE + 0x310)
#define AN8855_QP_DIG_MODE_CTRL_0		(AN8855_QP_DIG_CSR_BASE + 0x324)
#define AN8855_QP_DIG_MODE_CTRL_1		(AN8855_QP_DIG_CSR_BASE + 0x330)

#define AN8855_QP_PMA_TOP_BASE			0x1022e000
#define AN8855_PON_RXFEDIG_CTRL_0		(AN8855_QP_PMA_TOP_BASE + 0x100)
#define AN8855_PON_RXFEDIG_CTRL_9		(AN8855_QP_PMA_TOP_BASE + 0x124)

#define AN8855_SS_LCPLL_PWCTL_SETTING_2		(AN8855_QP_PMA_TOP_BASE + 0x208)
#define AN8855_SS_LCPLL_TDC_FLT_2		(AN8855_QP_PMA_TOP_BASE + 0x230)
#define AN8855_SS_LCPLL_TDC_FLT_5		(AN8855_QP_PMA_TOP_BASE + 0x23c)
#define AN8855_SS_LCPLL_TDC_PCW_1		(AN8855_QP_PMA_TOP_BASE + 0x248)
#define AN8855_INTF_CTRL_8			(AN8855_QP_PMA_TOP_BASE + 0x320)
#define AN8855_INTF_CTRL_9			(AN8855_QP_PMA_TOP_BASE + 0x324)
#define AN8855_PLL_CTRL_0			(AN8855_QP_PMA_TOP_BASE + 0x400)
#define AN8855_PLL_CTRL_2			(AN8855_QP_PMA_TOP_BASE + 0x408)
#define AN8855_PLL_CTRL_3			(AN8855_QP_PMA_TOP_BASE + 0x40c)
#define AN8855_PLL_CTRL_4			(AN8855_QP_PMA_TOP_BASE + 0x410)
#define AN8855_PLL_CK_CTRL_0			(AN8855_QP_PMA_TOP_BASE + 0x414)
#define AN8855_RX_DLY_0				(AN8855_QP_PMA_TOP_BASE + 0x614)
#define AN8855_RX_CTRL_2			(AN8855_QP_PMA_TOP_BASE + 0x630)
#define AN8855_RX_CTRL_5			(AN8855_QP_PMA_TOP_BASE + 0x63c)
#define AN8855_RX_CTRL_6			(AN8855_QP_PMA_TOP_BASE + 0x640)
#define AN8855_RX_CTRL_7			(AN8855_QP_PMA_TOP_BASE + 0x644)
#define AN8855_RX_CTRL_8			(AN8855_QP_PMA_TOP_BASE + 0x648)
#define AN8855_RX_CTRL_26			(AN8855_QP_PMA_TOP_BASE + 0x690)
#define AN8855_RX_CTRL_42			(AN8855_QP_PMA_TOP_BASE + 0x6d0)

#define AN8855_QP_ANA_CSR_BASE			0x1022f000
#define AN8855_RG_QP_RX_DAC_EN			(AN8855_QP_ANA_CSR_BASE + 0x00)
#define AN8855_RG_QP_RXAFE_RESERVE		(AN8855_QP_ANA_CSR_BASE + 0x04)
#define AN8855_RG_QP_CDR_LPF_MJV_LIM		(AN8855_QP_ANA_CSR_BASE + 0x0c)
#define AN8855_RG_QP_CDR_LPF_SETVALUE		(AN8855_QP_ANA_CSR_BASE + 0x14)
#define AN8855_RG_QP_CDR_PR_CKREF_DIV1		(AN8855_QP_ANA_CSR_BASE + 0x18)
#define AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE	(AN8855_QP_ANA_CSR_BASE + 0x1c)
#define AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF	(AN8855_QP_ANA_CSR_BASE + 0x20)
#define AN8855_RG_QP_TX_MODE_16B_EN		(AN8855_QP_ANA_CSR_BASE + 0x28)
#define AN8855_RG_QP_PLL_IPLL_DIG_PWR_SEL	(AN8855_QP_ANA_CSR_BASE + 0x3c)
#define AN8855_RG_QP_PLL_SDM_ORD		(AN8855_QP_ANA_CSR_BASE + 0x40)

#define AN8855_ETHER_SYS_BASE			0x1028c800
#define RG_GPHY_AFE_PWD				(AN8855_ETHER_SYS_BASE + 0x40)

#define AN8855_PKG_SEL				0x10000094
#define PAG_SEL_AN8855H				0x2

/* PHY LED Register bitmap of define */
#define PHY_LED_CTRL_SELECT			0x3e8
#define PHY_SINGLE_LED_ON_CTRL(i)		(0x3e0 + ((i) * 2))
#define PHY_SINGLE_LED_BLK_CTRL(i)		(0x3e1 + ((i) * 2))
#define PHY_SINGLE_LED_ON_DUR(i)		(0x3e9 + ((i) * 2))
#define PHY_SINGLE_LED_BLK_DUR(i)		(0x3ea + ((i) * 2))

#define PHY_PMA_CTRL				0x340

#define PHY_DEV1F				0x1f

#define PHY_LED_ON_CTRL(i)			(0x24 + ((i) * 2))
#define LED_ON_EN				BIT(15)
#define LED_ON_POL				BIT(14)
#define LED_ON_EVT_MASK				0x7f

/* LED ON Event */
#define LED_ON_EVT_FORCE			BIT(6)
#define LED_ON_EVT_LINK_HD			BIT(5)
#define LED_ON_EVT_LINK_FD			BIT(4)
#define LED_ON_EVT_LINK_DOWN			BIT(3)
#define LED_ON_EVT_LINK_10M			BIT(2)
#define LED_ON_EVT_LINK_100M			BIT(1)
#define LED_ON_EVT_LINK_1000M			BIT(0)

#define PHY_LED_BLK_CTRL(i)			(0x25 + ((i) * 2))
#define LED_BLK_EVT_MASK			0x3ff
/* LED Blinking Event */
#define LED_BLK_EVT_FORCE			BIT(9)
#define LED_BLK_EVT_10M_RX_ACT			BIT(5)
#define LED_BLK_EVT_10M_TX_ACT			BIT(4)
#define LED_BLK_EVT_100M_RX_ACT			BIT(3)
#define LED_BLK_EVT_100M_TX_ACT			BIT(2)
#define LED_BLK_EVT_1000M_RX_ACT		BIT(1)
#define LED_BLK_EVT_1000M_TX_ACT		BIT(0)

#define PHY_LED_BCR				(0x21)
#define LED_BCR_EXT_CTRL			BIT(15)
#define LED_BCR_CLK_EN				BIT(3)
#define LED_BCR_TIME_TEST			BIT(2)
#define LED_BCR_MODE_MASK			3
#define LED_BCR_MODE_DISABLE			0

#define PHY_LED_ON_DUR				0x22
#define LED_ON_DUR_MASK				0xffff

#define PHY_LED_BLK_DUR				0x23
#define LED_BLK_DUR_MASK			0xffff

#define PHY_LED_BLINK_DUR_CTRL			0x720

/* Definition of LED */
#define LED_ON_EVENT	(LED_ON_EVT_LINK_1000M | \
			LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M |\
			LED_ON_EVT_LINK_HD | LED_ON_EVT_LINK_FD)

#define LED_BLK_EVENT	(LED_BLK_EVT_1000M_TX_ACT | \
			LED_BLK_EVT_1000M_RX_ACT | \
			LED_BLK_EVT_100M_TX_ACT | \
			LED_BLK_EVT_100M_RX_ACT | \
			LED_BLK_EVT_10M_TX_ACT | \
			LED_BLK_EVT_10M_RX_ACT)

#define LED_FREQ				AIR_LED_BLK_DUR_64M

#define AN8855_NUM_PHYS				5
#define AN8855_NUM_PORTS			6
#define AN8855_PHY_ADDR(base, addr)		(((base) + (addr)) & 0x1f)

/* PHY LED Register bitmap of define */
#define PHY_LED_CTRL_SELECT			0x3e8
#define PHY_SINGLE_LED_ON_CTRL(i)		(0x3e0 + ((i) * 2))
#define PHY_SINGLE_LED_BLK_CTRL(i)		(0x3e1 + ((i) * 2))
#define PHY_SINGLE_LED_ON_DUR(i)		(0x3e9 + ((i) * 2))
#define PHY_SINGLE_LED_BLK_DUR(i)		(0x3ea + ((i) * 2))

/* AN8855 LED */
enum an8855_led_blk_dur {
	AIR_LED_BLK_DUR_32M,
	AIR_LED_BLK_DUR_64M,
	AIR_LED_BLK_DUR_128M,
	AIR_LED_BLK_DUR_256M,
	AIR_LED_BLK_DUR_512M,
	AIR_LED_BLK_DUR_1024M,
	AIR_LED_BLK_DUR_LAST
};

enum an8855_led_polarity {
	LED_LOW,
	LED_HIGH,
};

enum an8855_led_mode {
	AN8855_LED_MODE_DISABLE,
	AN8855_LED_MODE_USER_DEFINE,
	AN8855_LED_MODE_LAST
};

enum phy_led_idx {
	P0_LED0,
	P0_LED1,
	P0_LED2,
	P0_LED3,
	P1_LED0,
	P1_LED1,
	P1_LED2,
	P1_LED3,
	P2_LED0,
	P2_LED1,
	P2_LED2,
	P2_LED3,
	P3_LED0,
	P3_LED1,
	P3_LED2,
	P3_LED3,
	P4_LED0,
	P4_LED1,
	P4_LED2,
	P4_LED3,
	PHY_LED_MAX
};

struct an8855_led_cfg {
	u16 en;
	u8  phy_led_idx;
	u16 pol;
	u16 on_cfg;
	u16 blk_cfg;
	u8 led_freq;
};

struct an8855_switch_priv {
	struct mtk_eth_switch_priv epriv;
	struct mii_dev *mdio_bus;
	u32 phy_base;
};

/* AN8855 Reference Board */
static const struct an8855_led_cfg led_cfg[] = {
/*************************************************************************
 * Enable, LED idx, LED Polarity, LED ON event,  LED Blink event  LED Freq
 *************************************************************************
 */
	/* GPIO0 */
	{1, P4_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO1 */
	{1, P4_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO2 */
	{1, P0_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO3 */
	{1, P0_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO4 */
	{1, P1_LED0, LED_LOW,  LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO5 */
	{1, P1_LED1, LED_LOW,  LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO6 */
	{0, PHY_LED_MAX, LED_LOW,  LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO7 */
	{0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO8 */
	{0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO9 */
	{1, P2_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO10 */
	{1, P2_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO11 */
	{1, P3_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO12 */
	{1, P3_LED1, LED_HIGH,  LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO13 */
	{0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO14 */
	{0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO15 */
	{0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO16 */
	{0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO17 */
	{0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO18 */
	{0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO19 */
	{0, PHY_LED_MAX, LED_LOW,  LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
	/* GPIO20 */
	{0, PHY_LED_MAX, LED_LOW,  LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ},
};

static int __an8855_reg_read(struct mtk_eth_priv *priv, u8 phy_base, u32 reg, u32 *data)
{
	int ret, low_word, high_word;

	ret = mtk_mii_write(priv, phy_base, 0x1f, 0x4);
	if (ret)
		return ret;

	ret = mtk_mii_write(priv, phy_base, 0x10, 0);
	if (ret)
		return ret;

	ret = mtk_mii_write(priv, phy_base, 0x15, ((reg >> 16) & 0xFFFF));
	if (ret)
		return ret;

	ret = mtk_mii_write(priv, phy_base, 0x16, (reg & 0xFFFF));
	if (ret)
		return ret;

	low_word = mtk_mii_read(priv, phy_base, 0x18);
	if (low_word < 0)
		return low_word;

	high_word = mtk_mii_read(priv, phy_base, 0x17);
	if (high_word < 0)
		return high_word;

	ret = mtk_mii_write(priv, phy_base, 0x1f, 0);
	if (ret)
		return ret;

	ret = mtk_mii_write(priv, phy_base, 0x10, 0);
	if (ret)
		return ret;

	if (data)
		*data = ((u32)high_word << 16) | (low_word & 0xffff);

	return 0;
}

static int an8855_reg_read(struct an8855_switch_priv *priv, u32 reg, u32 *data)
{
	return __an8855_reg_read(priv->epriv.eth, priv->phy_base, reg, data);
}

static int an8855_reg_write(struct an8855_switch_priv *priv, u32 reg, u32 data)
{
	int ret;

	ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x1f, 0x4);
	if (ret)
		return ret;

	ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x10, 0);
	if (ret)
		return ret;

	ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x11,
			    ((reg >> 16) & 0xFFFF));
	if (ret)
		return ret;

	ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x12,
			    (reg & 0xFFFF));
	if (ret)
		return ret;

	ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x13,
			    ((data >> 16) & 0xFFFF));
	if (ret)
		return ret;

	ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x14,
			    (data & 0xFFFF));
	if (ret)
		return ret;

	ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x1f, 0);
	if (ret)
		return ret;

	ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x10, 0);
	if (ret)
		return ret;

	return 0;
}

static int an8855_phy_cl45_read(struct an8855_switch_priv *priv, int port,
				int devad, int regnum, u16 *data)
{
	u16 phy_addr = AN8855_PHY_ADDR(priv->phy_base, port);

	*data = mtk_mmd_ind_read(priv->epriv.eth, phy_addr, devad, regnum);

	return 0;
}

static int an8855_phy_cl45_write(struct an8855_switch_priv *priv, int port,
				 int devad, int regnum, u16 data)
{
	u16 phy_addr = AN8855_PHY_ADDR(priv->phy_base, port);

	mtk_mmd_ind_write(priv->epriv.eth, phy_addr, devad, regnum, data);

	return 0;
}

static int an8855_port_sgmii_init(struct an8855_switch_priv *priv, u32 port)
{
	u32 val = 0;

	if (port != 5) {
		printf("an8855: port %d is not a SGMII port\n", port);
		return -EINVAL;
	}

	/* PLL */
	an8855_reg_read(priv, AN8855_QP_DIG_MODE_CTRL_1, &val);
	val &= ~(0x3 << 2);
	val |= (0x1 << 2);
	an8855_reg_write(priv, AN8855_QP_DIG_MODE_CTRL_1, val);

	/* PLL - LPF */
	an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val);
	val &= ~(0x3 << 0);
	val |= (0x1 << 0);
	val &= ~(0x7 << 2);
	val |= (0x5 << 2);
	val &= ~GENMASK(7, 6);
	val &= ~(0x7 << 8);
	val |= (0x3 << 8);
	val |= BIT(29);
	val &= ~GENMASK(13, 12);
	an8855_reg_write(priv, AN8855_PLL_CTRL_2, val);

	/* PLL - ICO */
	an8855_reg_read(priv, AN8855_PLL_CTRL_4, &val);
	val |= BIT(2);
	an8855_reg_write(priv, AN8855_PLL_CTRL_4, val);

	an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val);
	val &= ~BIT(14);
	an8855_reg_write(priv, AN8855_PLL_CTRL_2, val);

	/* PLL - CHP */
	an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val);
	val &= ~(0xf << 16);
	val |= (0x6 << 16);
	an8855_reg_write(priv, AN8855_PLL_CTRL_2, val);

	/* PLL - PFD */
	an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val);
	val &= ~(0x3 << 20);
	val |= (0x1 << 20);
	val &= ~(0x3 << 24);
	val |= (0x1 << 24);
	val &= ~BIT(26);
	an8855_reg_write(priv, AN8855_PLL_CTRL_2, val);

	/* PLL - POSTDIV */
	an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val);
	val |= BIT(22);
	val &= ~BIT(27);
	val &= ~BIT(28);
	an8855_reg_write(priv, AN8855_PLL_CTRL_2, val);

	/* PLL - SDM */
	an8855_reg_read(priv, AN8855_PLL_CTRL_4, &val);
	val &= ~GENMASK(4, 3);
	an8855_reg_write(priv, AN8855_PLL_CTRL_4, val);

	an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val);
	val &= ~BIT(30);
	an8855_reg_write(priv, AN8855_PLL_CTRL_2, val);

	an8855_reg_read(priv, AN8855_SS_LCPLL_PWCTL_SETTING_2, &val);
	val &= ~(0x3 << 16);
	val |= (0x1 << 16);
	an8855_reg_write(priv, AN8855_SS_LCPLL_PWCTL_SETTING_2, val);

	an8855_reg_write(priv, AN8855_SS_LCPLL_TDC_FLT_2, 0x7a000000);
	an8855_reg_write(priv, AN8855_SS_LCPLL_TDC_PCW_1, 0x7a000000);

	an8855_reg_read(priv, AN8855_SS_LCPLL_TDC_FLT_5, &val);
	val &= ~BIT(24);
	an8855_reg_write(priv, AN8855_SS_LCPLL_TDC_FLT_5, val);

	an8855_reg_read(priv, AN8855_PLL_CK_CTRL_0, &val);
	val &= ~BIT(8);
	an8855_reg_write(priv, AN8855_PLL_CK_CTRL_0, val);

	/* PLL - SS */
	an8855_reg_read(priv, AN8855_PLL_CTRL_3, &val);
	val &= ~GENMASK(15, 0);
	an8855_reg_write(priv, AN8855_PLL_CTRL_3, val);

	an8855_reg_read(priv, AN8855_PLL_CTRL_4, &val);
	val &= ~GENMASK(1, 0);
	an8855_reg_write(priv, AN8855_PLL_CTRL_4, val);

	an8855_reg_read(priv, AN8855_PLL_CTRL_3, &val);
	val &= ~GENMASK(31, 16);
	an8855_reg_write(priv, AN8855_PLL_CTRL_3, val);

	/* PLL - TDC */
	an8855_reg_read(priv, AN8855_PLL_CK_CTRL_0, &val);
	val &= ~BIT(9);
	an8855_reg_write(priv, AN8855_PLL_CK_CTRL_0, val);

	an8855_reg_read(priv, AN8855_RG_QP_PLL_SDM_ORD, &val);
	val |= BIT(3);
	val |= BIT(4);
	an8855_reg_write(priv, AN8855_RG_QP_PLL_SDM_ORD, val);

	an8855_reg_read(priv, AN8855_RG_QP_RX_DAC_EN, &val);
	val &= ~(0x3 << 16);
	val |= (0x2 << 16);
	an8855_reg_write(priv, AN8855_RG_QP_RX_DAC_EN, val);

	/* TCL Disable (only for Co-SIM) */
	an8855_reg_read(priv, AN8855_PON_RXFEDIG_CTRL_0, &val);
	val &= ~BIT(12);
	an8855_reg_write(priv, AN8855_PON_RXFEDIG_CTRL_0, val);

	/* TX Init */
	an8855_reg_read(priv, AN8855_RG_QP_TX_MODE_16B_EN, &val);
	val &= ~BIT(0);
	val &= ~(0xffff << 16);
	val |= (0x4 << 16);
	an8855_reg_write(priv, AN8855_RG_QP_TX_MODE_16B_EN, val);

	/* RX Control */
	an8855_reg_read(priv, AN8855_RG_QP_RXAFE_RESERVE, &val);
	val |= BIT(11);
	an8855_reg_write(priv, AN8855_RG_QP_RXAFE_RESERVE, val);

	an8855_reg_read(priv, AN8855_RG_QP_CDR_LPF_MJV_LIM, &val);
	val &= ~(0x3 << 4);
	val |= (0x1 << 4);
	an8855_reg_write(priv, AN8855_RG_QP_CDR_LPF_MJV_LIM, val);

	an8855_reg_read(priv, AN8855_RG_QP_CDR_LPF_SETVALUE, &val);
	val &= ~(0xf << 25);
	val |= (0x1 << 25);
	val &= ~(0x7 << 29);
	val |= (0x3 << 29);
	an8855_reg_write(priv, AN8855_RG_QP_CDR_LPF_SETVALUE, val);

	an8855_reg_read(priv, AN8855_RG_QP_CDR_PR_CKREF_DIV1, &val);
	val &= ~(0x1f << 8);
	val |= (0xf << 8);
	an8855_reg_write(priv, AN8855_RG_QP_CDR_PR_CKREF_DIV1, val);

	an8855_reg_read(priv, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE, &val);
	val &= ~(0x3f << 0);
	val |= (0x19 << 0);
	val &= ~BIT(6);
	an8855_reg_write(priv, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE, val);

	an8855_reg_read(priv, AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF, &val);
	val &= ~(0x7f << 6);
	val |= (0x21 << 6);
	val &= ~(0x3 << 16);
	val |= (0x2 << 16);
	val &= ~BIT(13);
	an8855_reg_write(priv, AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val);

	an8855_reg_read(priv, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE, &val);
	val &= ~BIT(30);
	an8855_reg_write(priv, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE, val);

	an8855_reg_read(priv, AN8855_RG_QP_CDR_PR_CKREF_DIV1, &val);
	val &= ~(0x7 << 24);
	val |= (0x4 << 24);
	an8855_reg_write(priv, AN8855_RG_QP_CDR_PR_CKREF_DIV1, val);

	an8855_reg_read(priv, AN8855_PLL_CTRL_0, &val);
	val |= BIT(0);
	an8855_reg_write(priv, AN8855_PLL_CTRL_0, val);

	an8855_reg_read(priv, AN8855_RX_CTRL_26, &val);
	val &= ~BIT(23);
	val |= BIT(26);
	an8855_reg_write(priv, AN8855_RX_CTRL_26, val);

	an8855_reg_read(priv, AN8855_RX_DLY_0, &val);
	val &= ~(0xff << 0);
	val |= (0x6f << 0);
	val |= GENMASK(13, 8);
	an8855_reg_write(priv, AN8855_RX_DLY_0, val);

	an8855_reg_read(priv, AN8855_RX_CTRL_42, &val);
	val &= ~(0x1fff << 0);
	val |= (0x150 << 0);
	an8855_reg_write(priv, AN8855_RX_CTRL_42, val);

	an8855_reg_read(priv, AN8855_RX_CTRL_2, &val);
	val &= ~(0x1fff << 16);
	val |= (0x150 << 16);
	an8855_reg_write(priv, AN8855_RX_CTRL_2, val);

	an8855_reg_read(priv, AN8855_PON_RXFEDIG_CTRL_9, &val);
	val &= ~(0x7 << 0);
	val |= (0x1 << 0);
	an8855_reg_write(priv, AN8855_PON_RXFEDIG_CTRL_9, val);

	an8855_reg_read(priv, AN8855_RX_CTRL_8, &val);
	val &= ~(0xfff << 16);
	val |= (0x200 << 16);
	val &= ~(0x7fff << 14);
	val |= (0xfff << 14);
	an8855_reg_write(priv, AN8855_RX_CTRL_8, val);

	/* Frequency memter */
	an8855_reg_read(priv, AN8855_RX_CTRL_5, &val);
	val &= ~(0xfffff << 10);
	val |= (0x10 << 10);
	an8855_reg_write(priv, AN8855_RX_CTRL_5, val);

	an8855_reg_read(priv, AN8855_RX_CTRL_6, &val);
	val &= ~(0xfffff << 0);
	val |= (0x64 << 0);
	an8855_reg_write(priv, AN8855_RX_CTRL_6, val);

	an8855_reg_read(priv, AN8855_RX_CTRL_7, &val);
	val &= ~(0xfffff << 0);
	val |= (0x2710 << 0);
	an8855_reg_write(priv, AN8855_RX_CTRL_7, val);

	/* PCS Init */
	an8855_reg_read(priv, AN8855_RG_HSGMII_PCS_CTROL_1, &val);
	val &= ~BIT(30);
	an8855_reg_write(priv, AN8855_RG_HSGMII_PCS_CTROL_1, val);

	/* Rate Adaption */
	an8855_reg_read(priv, AN8855_RATE_ADP_P0_CTRL_0, &val);
	val &= ~BIT(31);
	an8855_reg_write(priv, AN8855_RATE_ADP_P0_CTRL_0, val);

	an8855_reg_read(priv, AN8855_RG_RATE_ADAPT_CTRL_0, &val);
	val |= BIT(0);
	val |= BIT(4);
	val |= GENMASK(27, 26);
	an8855_reg_write(priv, AN8855_RG_RATE_ADAPT_CTRL_0, val);

	/* Disable AN */
	an8855_reg_read(priv, AN8855_SGMII_REG_AN0, &val);
	val &= ~BIT(12);
	an8855_reg_write(priv, AN8855_SGMII_REG_AN0, val);

	/* Force Speed */
	an8855_reg_read(priv, AN8855_SGMII_STS_CTRL_0, &val);
	val |= BIT(2);
	val |= GENMASK(5, 4);
	an8855_reg_write(priv, AN8855_SGMII_STS_CTRL_0, val);

	/* bypass flow control to MAC */
	an8855_reg_write(priv, AN8855_MSG_RX_LIK_STS_0, 0x01010107);
	an8855_reg_write(priv, AN8855_MSG_RX_LIK_STS_2, 0x00000EEF);

	return 0;
}

static void an8855_led_set_usr_def(struct an8855_switch_priv *priv, u8 entity,
				   enum an8855_led_polarity pol, u16 on_evt,
				   u16 blk_evt, u8 led_freq)
{
	u32 cl45_data;

	if (pol == LED_HIGH)
		on_evt |= LED_ON_POL;
	else
		on_evt &= ~LED_ON_POL;

	/* LED on event */
	an8855_phy_cl45_write(priv, (entity / 4), 0x1e,
			      PHY_SINGLE_LED_ON_CTRL(entity % 4),
			      on_evt | LED_ON_EN);

	/* LED blink event */
	an8855_phy_cl45_write(priv, (entity / 4), 0x1e,
			      PHY_SINGLE_LED_BLK_CTRL(entity % 4),
			      blk_evt);

	/* LED freq */
	switch (led_freq) {
	case AIR_LED_BLK_DUR_32M:
		cl45_data = 0x30e;
		break;

	case AIR_LED_BLK_DUR_64M:
		cl45_data = 0x61a;
		break;

	case AIR_LED_BLK_DUR_128M:
		cl45_data = 0xc35;
		break;

	case AIR_LED_BLK_DUR_256M:
		cl45_data = 0x186a;
		break;

	case AIR_LED_BLK_DUR_512M:
		cl45_data = 0x30d4;
		break;

	case AIR_LED_BLK_DUR_1024M:
		cl45_data = 0x61a8;
		break;

	default:
		cl45_data = 0;
		break;
	}

	an8855_phy_cl45_write(priv, (entity / 4), 0x1e,
			      PHY_SINGLE_LED_BLK_DUR(entity % 4),
			      cl45_data);

	an8855_phy_cl45_write(priv, (entity / 4), 0x1e,
			      PHY_SINGLE_LED_ON_DUR(entity % 4),
			      (cl45_data >> 1));

	/* Disable DATA & BAD_SSD for port LED blink behavior */
	cl45_data = mtk_mmd_ind_read(priv->epriv.eth, (entity / 4), 0x1e, PHY_PMA_CTRL);
	cl45_data &= ~BIT(0);
	cl45_data &= ~BIT(15);
	an8855_phy_cl45_write(priv, (entity / 4), 0x1e, PHY_PMA_CTRL, cl45_data);
}

static int an8855_led_set_mode(struct an8855_switch_priv *priv, u8 mode)
{
	u16 cl45_data;

	an8855_phy_cl45_read(priv, 0, 0x1f, PHY_LED_BCR, &cl45_data);

	switch (mode) {
	case AN8855_LED_MODE_DISABLE:
		cl45_data &= ~LED_BCR_EXT_CTRL;
		cl45_data &= ~LED_BCR_MODE_MASK;
		cl45_data |= LED_BCR_MODE_DISABLE;
		break;

	case AN8855_LED_MODE_USER_DEFINE:
		cl45_data |= LED_BCR_EXT_CTRL;
		cl45_data |= LED_BCR_CLK_EN;
		break;

	default:
		printf("an8855: LED mode%d is not supported!\n", mode);
		return -EINVAL;
	}

	an8855_phy_cl45_write(priv, 0, 0x1f, PHY_LED_BCR, cl45_data);

	return 0;
}

static int an8855_led_set_state(struct an8855_switch_priv *priv, u8 entity,
				u8 state)
{
	u16 cl45_data = 0;

	/* Change to per port contorl */
	an8855_phy_cl45_read(priv, (entity / 4), 0x1e, PHY_LED_CTRL_SELECT,
			     &cl45_data);

	if (state == 1)
		cl45_data |= (1 << (entity % 4));
	else
		cl45_data &= ~(1 << (entity % 4));

	an8855_phy_cl45_write(priv, (entity / 4), 0x1e, PHY_LED_CTRL_SELECT,
			      cl45_data);

	/* LED enable setting */
	an8855_phy_cl45_read(priv, (entity / 4), 0x1e,
			     PHY_SINGLE_LED_ON_CTRL(entity % 4), &cl45_data);

	if (state == 1)
		cl45_data |= LED_ON_EN;
	else
		cl45_data &= ~LED_ON_EN;

	an8855_phy_cl45_write(priv, (entity / 4), 0x1e,
			      PHY_SINGLE_LED_ON_CTRL(entity % 4), cl45_data);

	return 0;
}

static int an8855_led_init(struct an8855_switch_priv *priv)
{
	u32 val, id, tmp_id = 0;
	int ret;

	ret = an8855_led_set_mode(priv, AN8855_LED_MODE_USER_DEFINE);
	if (ret) {
		printf("an8855: led_set_mode failed with %d!\n", ret);
		return ret;
	}

	for (id = 0; id < ARRAY_SIZE(led_cfg); id++) {
		ret = an8855_led_set_state(priv, led_cfg[id].phy_led_idx,
					   led_cfg[id].en);
		if (ret != 0) {
			printf("an8855: led_set_state failed with %d!\n", ret);
			return ret;
		}

		if (led_cfg[id].en == 1) {
			an8855_led_set_usr_def(priv,
					       led_cfg[id].phy_led_idx,
					       led_cfg[id].pol,
					       led_cfg[id].on_cfg,
					       led_cfg[id].blk_cfg,
					       led_cfg[id].led_freq);
		}
	}

	/* Setting for System LED & Loop LED */
	an8855_reg_write(priv, AN8855_RG_GPIO_OE, 0x0);
	an8855_reg_write(priv, AN8855_RG_GPIO_CTRL, 0x0);
	an8855_reg_write(priv, AN8855_RG_GPIO_L_INV, 0);

	an8855_reg_write(priv, AN8855_RG_GPIO_CTRL, 0x1001);
	an8855_reg_read(priv, AN8855_RG_GPIO_DATA, &val);
	val |= GENMASK(3, 1);
	val &= ~(BIT(0));
	val &= ~(BIT(6));
	an8855_reg_write(priv, AN8855_RG_GPIO_DATA, val);

	an8855_reg_read(priv, AN8855_RG_GPIO_OE, &val);
	val |= 0x41;
	an8855_reg_write(priv, AN8855_RG_GPIO_OE, val);

	/* Mapping between GPIO & LED */
	val = 0;
	for (id = 0; id < ARRAY_SIZE(led_cfg); id++) {
		/* Skip GPIO6, due to GPIO6 does not support PORT LED */
		if (id == 6)
			continue;

		if (led_cfg[id].en == 1) {
			if (id < 7)
				val |= led_cfg[id].phy_led_idx << ((id % 4) * 8);
			else
				val |= led_cfg[id].phy_led_idx << (((id - 1) % 4) * 8);
		}

		if (id < 7)
			tmp_id = id;
		else
			tmp_id = id - 1;

		if ((tmp_id % 4) == 0x3) {
			an8855_reg_write(priv,
					 AN8855_RG_GPIO_LED_SEL(tmp_id / 4),
					 val);
			val = 0;
		}
	}

	/* Turn on LAN LED mode */
	val = 0;
	for (id = 0; id < ARRAY_SIZE(led_cfg); id++) {
		if (led_cfg[id].en == 1)
			val |= 0x1 << id;
	}
	an8855_reg_write(priv, AN8855_RG_GPIO_LED_MODE, val);

	/* Force clear blink pulse for per port LED */
	an8855_phy_cl45_write(priv, 0, 0x1f, PHY_LED_BLINK_DUR_CTRL, 0x1f);
	udelay(1000);
	an8855_phy_cl45_write(priv, 0, 0x1f, PHY_LED_BLINK_DUR_CTRL, 0);

	return 0;
}

static void an8855_port_isolation(struct an8855_switch_priv *priv)
{
	u32 i;

	for (i = 0; i < AN8855_NUM_PORTS; i++) {
		/* Set port matrix mode */
		if (i != 5)
			an8855_reg_write(priv, AN8855_PORTMATRIX_REG(i), 0x20);
		else
			an8855_reg_write(priv, AN8855_PORTMATRIX_REG(i), 0x1f);

		/* Set port mode to user port */
		an8855_reg_write(priv, AN8855_PVC(i),
				 (0x8100 << AN8855_STAG_VPID_S) |
				 (VLAN_ATTR_USER << AN8855_VLAN_ATTR_S));
	}
}

static void an8855_mac_control(struct mtk_eth_switch_priv *swpriv, bool enable)
{
	struct an8855_switch_priv *priv = (struct an8855_switch_priv *)swpriv;
	u32 pmcr = AN8855_FORCE_MODE_LNK;

	if (enable)
		pmcr = AN8855_FORCE_MODE;

	an8855_reg_write(priv, AN8855_PMCR_REG(5), pmcr);
}

static int an8855_mdio_read(struct mii_dev *bus, int addr, int devad, int reg)
{
	struct an8855_switch_priv *priv = bus->priv;

	if (devad < 0)
		return mtk_mii_read(priv->epriv.eth, addr, reg);

	return mtk_mmd_ind_read(priv->epriv.eth, addr, devad, reg);
}

static int an8855_mdio_write(struct mii_dev *bus, int addr, int devad, int reg,
			     u16 val)
{
	struct an8855_switch_priv *priv = bus->priv;

	if (devad < 0)
		return mtk_mii_write(priv->epriv.eth, addr, reg, val);

	return mtk_mmd_ind_write(priv->epriv.eth, addr, devad, reg, val);
}

static int an8855_mdio_register(struct an8855_switch_priv *priv)
{
	struct mii_dev *mdio_bus = mdio_alloc();
	int ret;

	if (!mdio_bus)
		return -ENOMEM;

	mdio_bus->read = an8855_mdio_read;
	mdio_bus->write = an8855_mdio_write;
	snprintf(mdio_bus->name, sizeof(mdio_bus->name), priv->epriv.sw->name);

	mdio_bus->priv = priv;

	ret = mdio_register(mdio_bus);
	if (ret) {
		mdio_free(mdio_bus);
		return ret;
	}

	priv->mdio_bus = mdio_bus;

	return 0;
}

static int an8855_setup(struct mtk_eth_switch_priv *swpriv)
{
	struct an8855_switch_priv *priv = (struct an8855_switch_priv *)swpriv;
	u16 phy_addr, phy_val;
	u32 i, id, val = 0;
	int ret;

	priv->phy_base = 1;

	/* Turn off PHYs */
	for (i = 0; i < AN8855_NUM_PHYS; i++) {
		phy_addr = AN8855_PHY_ADDR(priv->phy_base, i);
		phy_val = mtk_mii_read(priv->epriv.eth, phy_addr, MII_BMCR);
		phy_val |= BMCR_PDOWN;
		mtk_mii_write(priv->epriv.eth, phy_addr, MII_BMCR, phy_val);
	}

	/* Force MAC link down before reset */
	an8855_reg_write(priv, AN8855_PMCR_REG(5), AN8855_FORCE_MODE_LNK);

	/* Switch soft reset */
	an8855_reg_write(priv, AN8855_SYS_CTRL_REG, AN8855_SW_SYS_RST);
	mdelay(100);

	an8855_reg_read(priv, AN8855_PKG_SEL, &val);
	if ((val & 0x7) == PAG_SEL_AN8855H) {
		/* Release power down */
		an8855_reg_write(priv, RG_GPHY_AFE_PWD, 0x0);

		/* Invert for LED activity change */
		an8855_reg_read(priv, AN8855_RG_GPIO_L_INV, &val);
		for (id = 0; id < ARRAY_SIZE(led_cfg); id++) {
			if (led_cfg[id].pol == LED_HIGH && led_cfg[id].en == 1)
				val |= 0x1 << id;
		}
		an8855_reg_write(priv, AN8855_RG_GPIO_L_INV, (val | 0x1));

		/* MCU NOP CMD */
		an8855_reg_write(priv, AN8855_RG_GDMP_RAM, 0x846);
		an8855_reg_write(priv, AN8855_RG_GDMP_RAM + 4, 0x4a);

		/* Enable MCU */
		an8855_reg_read(priv, AN8855_RG_CLK_CPU_ICG, &val);
		an8855_reg_write(priv, AN8855_RG_CLK_CPU_ICG,
				 val | AN8855_MCU_ENABLE);
		udelay(1000);

		/* Disable MCU watchdog */
		an8855_reg_read(priv, AN8855_RG_TIMER_CTL, &val);
		an8855_reg_write(priv, AN8855_RG_TIMER_CTL,
				 (val & (~AN8855_WDOG_ENABLE)));

		/* LED settings for T830 reference board */
		ret = an8855_led_init(priv);
		if (ret < 0) {
			printf("an8855: an8855_led_init failed with %d\n", ret);
			return ret;
		}
	}

	switch (priv->epriv.phy_interface) {
	case PHY_INTERFACE_MODE_2500BASEX:
		an8855_port_sgmii_init(priv, 5);
		break;

	default:
		break;
	}

	an8855_reg_read(priv, AN8855_CKGCR, &val);
	val &= ~(0x3);
	an8855_reg_write(priv, AN8855_CKGCR, val);

	/* Enable port isolation to block inter-port communication */
	an8855_port_isolation(priv);

	/* Turn on PHYs */
	for (i = 0; i < AN8855_NUM_PHYS; i++) {
		phy_addr = AN8855_PHY_ADDR(priv->phy_base, i);
		phy_val = mtk_mii_read(priv->epriv.eth, phy_addr, MII_BMCR);
		phy_val &= ~BMCR_PDOWN;
		mtk_mii_write(priv->epriv.eth, phy_addr, MII_BMCR, phy_val);
	}

	return an8855_mdio_register(priv);
}

static int an8855_cleanup(struct mtk_eth_switch_priv *swpriv)
{
	struct an8855_switch_priv *priv = (struct an8855_switch_priv *)swpriv;

	mdio_unregister(priv->mdio_bus);

	return 0;
}

static int an8855_detect(struct mtk_eth_priv *priv)
{
	int ret;
	u32 val;

	ret = __an8855_reg_read(priv, 1, 0x10005000, &val);
	if (ret)
		return ret;

	if (val == 0x8855)
		return 0;

	return -ENODEV;
}

MTK_ETH_SWITCH(an8855) = {
	.name = "an8855",
	.desc = "Airoha AN8855",
	.priv_size = sizeof(struct an8855_switch_priv),
	.reset_wait_time = 100,

	.detect = an8855_detect,
	.setup = an8855_setup,
	.cleanup = an8855_cleanup,
	.mac_control = an8855_mac_control,
};
